﻿/**
 * Create in 2012-3-29 上午9:55:56.
 */
package kit4j.common.lang;

/**
 * 固定长度的数值类型。该类型确保在创建以后其
 * <code>{@link #getNumberAsString() getNumberAsString}</code>
 * 方法返回的字符串格式的数值长度始终固定不变。
 * 
 * @author MiniKnife
 * 
 */
public abstract class FixedLengthNumber extends Number {

	private static final long serialVersionUID = 1L;

	/**
	 * 数值的长度。
	 */
	protected int length;

	/**
	 * 数值的字符串形式。
	 */
	protected String numberAsString;

	/**
	 * 是否忽略数值长度超出当前设置长度时抛出的异常。
	 */
	protected boolean ignoreNumberOutOfBoundException;

	/**
	 * 数值字符串的构建器。
	 */
	private StringBuilder numberBuilder;

	/**
	 * 创建一个指定长度的数值。
	 * 
	 * @param length
	 *            长度
	 */
	public FixedLengthNumber(int length) {
		this.length = length;

		initNumberBuilder();
	}

	private void initNumberBuilder() {
		numberBuilder = new StringBuilder();

		for (int i = 0; i < length; ++i) {
			numberBuilder.append('0');
		}
	}

	/**
	 * @return 数值的长度
	 */
	public int getLength() {
		return length;
	}

	/**
	 * @return 以字符串的形式返回数值
	 */
	public String getNumberAsString() {
		return numberBuilder.toString();
	}

	/**
	 * 设置当前数值为指定字符串所指代的值。
	 * 
	 * @param str
	 *            字符串
	 * @throws NumberFormatException
	 *             如果字符串不能被解析为正确的数值。
	 * @throws NumberOutOfBoundException
	 *             如果
	 *             <code>{@link #isIgnoreNumberOutOfBoundException isIgnoreNumberOutOfBoundException()}</code>
	 *             返回false且字符串所指代数值的长度超出了当前设置的数值长度。
	 */
	public void setNumberByString(String str) throws NumberFormatException,
			NumberOutOfBoundException {
		Number num = parseNumberFromString(str);
		numberAsString = parseStringFromNumber(num);

		setNumberByFormattedString(numberAsString);
	}

	/**
	 * 将字符串参数作为数值进行解析。
	 * 
	 * @param str
	 *            字符串
	 * @return 通过字符串解析出的数值
	 * @throws NumberFormatException
	 *             如果字符串不能被解析为正确的数值。
	 */
	abstract protected Number parseNumberFromString(String str)
			throws NumberFormatException;

	/**
	 * 将指定的数值解析为字符串形式。
	 * 
	 * @param num
	 *            要解析的数值
	 * @return 数值解析出的字符串
	 */
	abstract protected String parseStringFromNumber(Number num);

	/**
	 * 设置当前数值为格式化以后的字符串所指代的值。
	 * 
	 * @param formattedString
	 *            格式化以后的字符串
	 * @throws NumberOutOfBoundException
	 *             如果
	 *             <code>{@link #isIgnoreNumberOutOfBoundException isIgnoreNumberOutOfBoundException()}</code>
	 *             返回false且字符串所指代数值的长度超出了当前设置的数值长度。
	 */
	protected void setNumberByFormattedString(String formattedString)
			throws NumberOutOfBoundException {
		if (!isIgnoreNumberOutOfBoundException()
				&& formattedString.length() > length) {
			throw new NumberOutOfBoundException("number length out of bound: "
					+ formattedString.length());
		}

		for (int i = length - 1, j = formattedString.length() - 1; i >= 0; --i, --j) {
			if(j < 0) {
				numberBuilder.setCharAt(i, '0');
			} else {
				numberBuilder.setCharAt(i, formattedString.charAt(j));
			}
		}
		
		if(formattedString.contains("-")) {
			int index = numberBuilder.indexOf("-");
			
			numberBuilder.replace(index, index + 1, "0");
			numberBuilder.setCharAt(0, '-');
		}
	}

	/**
	 * @return 是否忽略数值超出长度时抛出的异常。默认为false。
	 */
	public boolean isIgnoreNumberOutOfBoundException() {
		return ignoreNumberOutOfBoundException;
	}

	/**
	 * 设置是否忽略数值超出长度时抛出的异常。
	 * 
	 * @param ignoreNumberOutOfBoundException
	 *            如果要忽略，则为true，否则为false
	 */
	public void setIgnoreNumberOutOfBoundException(
			boolean ignoreNumberOutOfBoundException) {
		this.ignoreNumberOutOfBoundException = ignoreNumberOutOfBoundException;
	}

	/**
	 * 数值自增。
	 * 
	 * @throws NumberOutOfBoundException
	 *             如果
	 *             <code>{@link #isIgnoreNumberOutOfBoundException isIgnoreNumberOutOfBoundException()}</code>
	 *             返回false且自增后的数值的长度超出了当前设置的数值长度。
	 */
	abstract public void increase() throws NumberOutOfBoundException;

	/**
	 * 数值自减。
	 * 
	 * @throws NumberOutOfBoundException
	 *             如果
	 *             <code>{@link #isIgnoreNumberOutOfBoundException isIgnoreNumberOutOfBoundException()}</code>
	 *             返回false且自减后的数值的长度超出了当前设置的数值长度。
	 */
	abstract public void decrease() throws NumberOutOfBoundException;

	/**
	 * @return 当前数值
	 */
	public Number getNumber() {
		return parseNumberFromString(numberBuilder.toString());
	}

	/**
	 * 设置当前数值指定的值。
	 * 
	 * @param num
	 *            要指定的数值
	 * @throws NumberOutOfBoundException
	 *             如果
	 *             <code>{@link #isIgnoreNumberOutOfBoundException isIgnoreNumberOutOfBoundException()}</code>
	 *             返回false且数值的长度超出了当前设置的数值长度。
	 */
	public void setNumber(Number num) throws NumberOutOfBoundException {
		numberAsString = parseStringFromNumber(num);

		setNumberByFormattedString(numberAsString);
	}

}
